Enunciado

Bienvenidos a la Actividad 1, donde pondremos en práctica todo lo aprendido durante el bloque 2. Esta actividad la realizaremos en clase, se terminará en casa (debería completarse en clase) y se entregará el día 8 de octubre.

¿En qué consiste?

Vamos a poner en práctica cuatro aspectos del procesamiento de imágenes:

La finalidad es sencilla. Se os dará una imagen, a color, que tiene varias tonalidades y que está pintada con círculos.

La actividad consiste en contar el número de círculos de la imagen. image.png

Evaluación

Se evaluará de la siguiente manera:

Formato de entrega

No se aceptará el formato .ipynb Habilitaré una actividad en Canvas para que podáis subir ambos archivos.

Inicialización

En primer lugar, cargamos todos los paquetes/frameworks que nos van a hacer falta. Se recomienda visitar la web: https://scikit-image.org/ para ver todas las funcionalidades que permite Scikit Image.

Cargar la imagen

Lo primero de todo, vamos a leer la imagen. Recuerda que hay que subir la imagen cada vez que inicies sesión en el notebook y que la ruta se mira haciendo botón derecho sobre el archivo.

Con lo cual, aquí vamos a hacer dos cosas:

Hacemos esto para luego posteriormente umbralizar la imagen en escala de grises.

Se importa rg2gray de la biblioteca skimage.color y gracias a ella, podemos poner la imagen en escala de grises y, como ya hemos visto antes, empleamos la función plt. para mostrar la imagen.

Umbralizar la imagen con varios métodos

Vamos a probar ahora diferentes métodos para umbralizar la imagen. Se pide en esta actividad:

UMBRALIZACIÓN OTSU

threshold_otsu es una función que implementa el algoritmo de umbralización de Otsu. Por otro lado, imagen_gris es la imagen en escala de grises en la que deseas calcular el umbral de Otsu. Antes de usar la función, hemos convertido a escala de grises porque hace más sencilla la umbralización. Esto se debe a que solo se necesita un valor de intensidad por píxel en lugar de tres valores (R, G y B).

El umbral de Otsu divide la imagen en dos clases: píxeles que están por debajo del umbral y píxeles que están por encima.

Los píxeles por debajo del umbral se establecen en un valor (generalmente 0 o negro) y los píxeles por encima del umbral se establecen en otro valor (generalmente 255 o blanco) para separar objetos o características de interés en la imagen.

Por último he usado las funciones plt. para mostrar la imagen.

UMBRALIZACIÓN LOCAL

En este código tenemos block_size = 51. Esto define el tamaño de la ventana local que se va a usar para calcular el umbral local. La ventana local es un área de la imagen en la que se calcula un umbral diferente para cada bloque de píxeles. En este caso, como block_size es 51 se calculará un umbral local basado en un bloque de 51x51 píxeles en la imagen.

imagen_umbralizada = threshold_local(imagen_gris, block_size): Esta línea aplica la umbralización local a la imagen en escala de grises imagen_gris. La umbralización local calcula umbrales diferentes para diferentes regiones de la imagen. Es una función útil cunado la iluminación es irregular o cuando deseas segmentar objetos de diferentes tamaños en la imagen. Cada píxel en imagen_umbralizada se asignará a 0 o 1 según si supera o no el umbral local en su vecindario correspondiente.

UMBRALIZACIÓN CON NIBLACK

window_size = 47: define el tamaño de la ventana que se utilizará para calcular el umbral de Niblack. La ventana es un área cuadrada de la imagen que se desplaza por la imagen para calcular diferentes umbrales locales. En este caso es 47, lo que significa que se utilizará una ventana cuadrada de 47x47 píxeles.

imagen_umbralizada = threshold_niblack(imagen_gris, window_size=window_size) aplica el umbral de Niblack a la imagen en escala de grises.

Como en los casos anteriores, cada píxel se asignará a 0 o 1 según si supera o no el umbral de Niblack La umbralización de Niblack es útil en casos donde la iluminación es variable o desigual en diferentes partes de la imagen.

UMBRALIZACIÓN SAUVOLA

window_size: Es el tamaño de la ventana que se usa para calcular el umbral de Sauvola.

Cada píxel en imagen_umbralizada se asignará a 0 o 1 según si supera o no el umbral de Sauvola calculado en su vecindario correspondiente.

El umbral de Sauvola es un enfoque de umbralización local que considera tanto el valor medio de la intensidad en un vecindario como su desviación estándar local. Esto lo hace adecuado para imágenes con variaciones en la iluminación local, ya que adapta el umbral localmente en función de la información estadística en el área circundante de cada píxel.

Los métodos threshold_niblack y threshold_sauvola corresponden a la umbralización adaptativa. Ésta permite segmentar muy bien objetos de imagenes que tienen iluminación irregular o variaciones locales de contraste.

IMAGEN GIRADA

Sí, se obtiene la misma imagen. Únicamente está girada 180º, pero la imagen no presenta otros cambios representativos.

De todos los métodos de umbralización usados, con el que mejor resultado se ha optenido es con threshold_local ya que es la que más se parece a la que decía el ejercicio que era el mejor resultado a alcanzar.

Morfología Matemática

Como se puede apreciar en la imagen hay varios elementos imperfectos:

Mediante el uso de morfología matemática (concretamente los cuatro operadores visto en clase) y los posibles elementos estructurales existentes, se pide:

EROSIÓN

La erosión es una operación morfológica utilizada en el procesamiento de imágenes para reducir el tamaño de los objetos en una imagen binaria.

DILATACIÓN

La dilatación es una operación morfológica utilizada en el procesamiento de imágenes para aumentar el tamaño de los objetos en una imagen binaria.

APERTURA

La función apertura es una operación morfológica usada en el procesamiento de imágenes que se suele utilizar para limpiar el ruido en las imágenes binarias, eliminar pequeños objetos no deseados y separar objetos que están muy cerca entre sí. Esta operación se realiaz haciendo primero una erosión y seguidamente una dilatación.

CLAUSURA

La operación de clausura se suele usar para cerrar pequeños agujeros en los objetos y para conectar componentes que pueden estar separados en la imagen binaria original. Esto puede ser útil para el procesamiento de imágenes y análisis de objetos.

RESULTADO

Elegiría como mejor imagen cualquiera de las tres últimas funciones (dilatación, apertura o clausura) haya que todas son prácticamente iguales. Descartaría la imagen obtenida con erosión ya que se ve demasiado oscura. Para el siguiente ejercicio he decidido seleccionar la apertura (aunque me hubiee valido cualquiera de los otros dos nombrado por lo ya explicado).

Contando círculos

Haciendo uso de las funcionalidades cargadas al principio, se pide hacer una función que:

Por último, ¿qué se podría hacer para asegurar que no se tienen en cuenta posibles errores en la umbralización como pequeños puntos o posible ruido que haya llegado hasta este punto?

(Bonus) Automatizamos el proceso de extracción

Esta sección no es obligatoria pero la pongo para aquellos que quieran saber "¿y ahora qué se haría?".

Lo que hemos hecho hasta ahora es:

Es decir, tenemos varios parámetros y tenemos una función que nos dice cuál es el número de puntos dada una imagen. Variando dichos parámetros, variará también el número de puntos, pero no parece haber una relación directa.

También no hay que olvidar que desconocemos el número de puntos (nunca se ha dicho, aunque siempre puedes contarlos), por lo que no podemos seguir un proceso de aprendizaje supervisado (tipo descenso del gradiente sobre los parámetros anteriores para encontrar el mejor resultado).

Pero lo que sí podemos hacer es iterar el valor de los parámetros para alcanzar un máximo de puntos (asumiendo que dicho máximo corresponderá con el mejor resultado). Esto suele hacerse cuando no sabemos exáctamente el resultado que esperamos.

En definitiva, ahora se buscaría realizar un proceso iterativo para encontrar el valor máximo del número de puntos. Para ello haría falta:

Podría decirse que esa combinación de parámetros es la mejor.